import { useState } from 'react';
import * as mobx from 'mobx';
import { Watchable } from './type';
import { FormModel } from './model';
import { toJS } from 'mobx';
import { Field } from './model';
import { AsyncValue } from './helpers/AsyncValue';

function isNumericKey(key: string) {
  return String(Number.parseInt(key)) === key;
}

export function keyToValueShape(key: string) {
  return isNumericKey(key) ? 'array' : 'object';
}

export function splitToPath(name: number | string) {
  return String(name).split('.');
}

/** 合并两个值，如果第一个不为 undefined，则使用第一个值，否则使用第二个值 */
export function composeValue<T>(first: T, second: T) {
  if (first !== undefined) {
    return first;
  }
  return second;
}

/** lodash.get(...) for mobx observables */
export function observableGetIn(obj: any, key: string | string[], defaultValue?: any) {
  const path = Array.isArray(key) ? key : splitToPath(key);
  let target = obj;

  for (let i = 0; i < path.length; i += 1) {
    if (!mobx.isObservable(target)) {
      return defaultValue;
    }
    target = mobx.get(target, path[i]);
  }
  if (target === undefined) {
    return defaultValue;
  }
  return target;
}

/** lodash.set(...) for mobx observables */
export function observableSetIn(obj: unknown, key: string | string[], value: unknown) {
  // eslint-disable-next-line @typescript-eslint/no-use-before-define
  const path = Array.isArray(key) ? key : splitToPath(key);
  const lastPartIndex = path.length - 1;
  let target = obj;
  for (let i = 0; i < lastPartIndex; i += 1) {
    const part = path[i];
    if (mobx.get(target, part) == null) {
      if (isNumericKey(path[i + 1])) {
        mobx.set(target, part, []);
      } else {
        mobx.set(target, part, {});
      }
    }
    target = mobx.get(target, part);
    if (!mobx.isObservable(target)) {
      return;
    }
  }
  if (mobx.isObservable(target)) {
    mobx.set(target, path[lastPartIndex], value);
  }
}

function generateRandomId(len: number) {
  const byteToHex = (byte: number) => ('0' + byte.toString(16)).slice(-2);
  const arr = new Uint8Array(len / 2);
  window.crypto.getRandomValues(arr);
  return Array.from(arr, byteToHex).join('');
}

export function useHtmlIdPrefix(htmlIdPrefixProp?: string) {
  const [autoGeneratedPrefix] = useState(() => `xform_${generateRandomId(6)}.`);

  if (htmlIdPrefixProp !== undefined) {
    return htmlIdPrefixProp;
  }
  return autoGeneratedPrefix;
}

export function asCSSLength(len: number | string) {
  return typeof len === 'number' ? `${len}px` : len;
}

export function isFalsyOrEmptyArray(value: any) {
  return !value || (Array.isArray(value) && value.length === 0);
}

export const range = (n: number) => {
  const result: number[] = [];
  for (let i = 0; i < n; i++) {
    result.push(i);
  }
  return result;
};

export function pick<T extends object, K extends string>(
  obj: T,
  keys: K[]
): { [P in K]?: P extends keyof T ? T[P] : never } {
  const result: any = {};
  keys.forEach((key) => {
    if (key in obj) {
      result[key] = (obj as any)[key];
    }
  });
  return result;
}

export function convertWatchableToExpression(watch: Watchable, model: FormModel<any>) {
  if (typeof watch === 'string') {
    return () => toJS(model.getValue(watch));
  } else if (typeof watch === 'function') {
    return watch;
  } else if (watch instanceof Field) {
    return () => watch.value;
  } else if (watch instanceof FormModel) {
    return () => toJS(watch.values);
  } else if (watch instanceof AsyncValue) {
    return () => watch.current;
  } else if (Array.isArray(watch)) {
    return () => {
      return watch.map((t) => {
        if (typeof t === 'string') {
          return toJS(model.getValue(t));
        } else if (t instanceof AsyncValue) {
          return t.current;
        } else if (t instanceof Field) {
          return t.value;
        } else if (t instanceof FormModel) {
          return toJS(t.values);
        }
      }) as any;
    };
  } else {
    console.warn('[xform] 无法识别的 watch 参数', watch);
  }
}
